#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <libspe2.h>
#include <malloc_align.h>
#include <pthread.h>
#include <communicator.h>
#include <csp_shared_types.h>
#include <csp_protocol_msg.h>
#include <csp_macros.h>
#include <shared_data.h>
#include <csp_debug.h>
#include <ProcessComposition.h>
#include <Scheduler.h>
#include <LocalStoreManager.h>
#include <ProtocolHandler.h>
#include <PCBCache.h>


extern ea_addr_t shared_data;

void handle_spe_err(spe_stop_info_t *stop_info);
void *ppu_pthread_function(void *arg);
void mbox_write(spe_context_ptr_t *ctx, uint32_t sig);

typedef struct thread_args{
	spe_stop_info_t *stop_info;
	spe_context_ptr_t ctx;
	spe_args_t spe_args __attribute__ ((aligned(16)));
} thread_args_t;

void read_mbox_blocking(spe_context_ptr_t *ctx, uint32_t *msg){
	spe_out_intr_mbox_read(*ctx, msg, 1, SPE_MBOX_ALL_BLOCKING);
}


/* Thread responsible for handling communication with a SPE asigned #ID. */
void 
spe_communicator(int spe_id, spe_program_handle_t *program)
{
	uint32_t message;
	pthread_t thread;
	thread_args_t t_args;
	spe_context_ptr_t ctx;
	ea_addr_t *pcb_pointers;
	spe_stop_info_t stop_info;

	
	DEBUG_PRINTF("THREAD: %d\n", spe_id);

	pcb_pointers = (ea_addr_t *) _malloc_align(sizeof(ea_addr_t)*MAX_SPU_SLOTS, 7);

	/* Arguments for SPE */ 
	t_args.spe_args.process_data_EA = (ea_addr_t) pcb_pointers ;
	t_args.spe_args.spe_id = spe_id;
	t_args.stop_info = &stop_info;
	/* spe places proces-mnemonic hashes here */
	t_args.spe_args.offload_area_ea = 
		(ea_addr_t) _malloc_align(SILLY_MAGIC_NUMBER*sizeof(uint64_t), 7);
	/* Pointer to the first pcb */
	PCBCache *pcbcache = PCBCache::Instance();
	t_args.spe_args.pcb_head = (ea_addr_t) pcbcache->Head();
	t_args.spe_args.shared_addr = shared_data;


	ProtocolHandler protocol(&ctx, &t_args.spe_args);


	/* Create SPE-threads to execute 'dispatcher' */
	/* Create context */
	if((ctx = spe_context_create(0, NULL)) == NULL){
		perror("Failed creating context");
		exit(1);
	}

	/* Load program into context */
	if(spe_program_load(ctx, program)){
		perror("Failed loading program");
		exit(1);
	}

	/* Create thread for SPE context */
	t_args.ctx = ctx;
	if(pthread_create(&thread, NULL, &ppu_pthread_function, &t_args)){
		perror("Failed creating thread");
		exit(1);
	}

	message = FETCH_FROM_SCHEDULER;

	/*****       Start communicating with SPE      ******/
	protocol.Talk();

	/* Wait for spe-starter-thread to complete. */
	DEBUG_PRINTF("#%d: Waiting for SPE to exit..\n", spe_id);
	if(pthread_join(thread, NULL)){
		perror("Failed pthread_join");
		exit(1);
	}
	handle_spe_err(&stop_info);
	pthread_exit(NULL);
}


void handle_spe_err(spe_stop_info_t *stop_info)
{
	// result is a union that holds the SPE output result
	int result=stop_info->result.spe_exit_code;

	//if( SPE_EXIT == stop_info->stop_reason ){return;} /* Good. */

	switch (stop_info->stop_reason) {
		case SPE_EXIT:
			printf("SPE stop_reason=SPE_EXIT, exit_code=");
			break;
		case SPE_STOP_AND_SIGNAL:
			printf("SPE stop_reason=SPE_STOP_AND_SIGNAL, signal_code=");
			result = stop_info->result.spe_signal_code;
			break;
		case SPE_RUNTIME_ERROR:
			printf("SPE stop_reason=SPE_RUNTIME_ERROR, runtime_error=");
			break;
		case SPE_RUNTIME_EXCEPTION:
			printf("SPE stop_reason=SPE_RUNTIME_EXCEPTION, runtime_exception=");
			break;
		case SPE_RUNTIME_FATAL:
			printf("SPE stop_reason=SPE_RUNTIME_FATAL, runtime_fatal=");
			break;
		case SPE_CALLBACK_ERROR:
			printf("SPE stop_reason=SPE_CALLBACK_ERROR callback_error=");
			break;
		default:
			printf("SPE stop_reason=UNKNOWN, result=\n"); break;
	}
	printf("%d, status=%x\n",result,stop_info->spu_status);
}

void mbox_write(spe_context_ptr_t *ctx, uint32_t sig){
	uint32_t msg = sig;
	int status = spe_in_mbox_write(*ctx, &msg, 1, SPE_MBOX_ALL_BLOCKING);
	if(1 != status){
		perror("Command to SPE not delivered.");
		exit(1);
	}
}


/* Thread to start and shut down spe context */
void *ppu_pthread_function(void *arg){
	unsigned int entry = SPE_DEFAULT_ENTRY;

	thread_args_t t_args = *((thread_args_t *) arg);
	spe_context_ptr_t ctx = t_args.ctx;
	spe_args_t spe_args  = t_args.spe_args;;

	if(spe_context_run(ctx, &entry, 0, &spe_args, NULL, t_args.stop_info) < 0){
		perror("Failed running context");
		exit(1);
	}

	if(spe_context_destroy( ctx )){
		perror("Failed to destroy context");
		exit(1);
	}

	//printf("Context(%i) destroyed.\n", spe_args.spe_id );
	pthread_exit(0);
}




